package com.sunday.common.mongo.query;

import com.sunday.common.core.lambda.toolkit.support.SFunction;
import com.sunday.common.mongo.core.MongoFieldMapping;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.query.Query;

/**
 * 查询过滤器
 *
 * @author zsy
 * @since 2020/1/6
 */
@Slf4j
public class QueryWrapper {

    @Getter
    private transient Query query;

    public QueryWrapper() {
        this(new CriteriaWrapper());
    }

    public QueryWrapper(CriteriaWrapper criteriaWrapper) {
        assert criteriaWrapper != null : "criteriaWrapper must not be null";
        this.query = new Query(criteriaWrapper.getCriteria());
    }

    public static QueryWrapper query() {
        return new QueryWrapper();
    }

    public static QueryWrapper query(CriteriaWrapper wrapper) {
        return new QueryWrapper(wrapper);
    }

    /**
     * 查询字段过滤
     *
     * @param columns
     * @return {@link QueryWrapper}
     */
    public <T> QueryWrapper addFieldFilter(SFunction<T, ?>... columns) {
        assert columns != null : "columns must not be null";
        for (SFunction<T, ?> sf : columns) {
            query.fields().include(MongoFieldMapping.getColumnName(sf));
        }
        return this;
    }

    /**
     * 添加查询过滤器
     *
     * @param fieldNames
     * @return {@link QueryWrapper}
     */
    public QueryWrapper addFieldFilter(String... fieldNames) {
        assert fieldNames != null : "fieldNames must not be null";
        for (String fieldName : fieldNames) {
            query.fields().include(fieldName);
        }
        return this;
    }

    /**
     * 获取条数
     * 等同于mysql limit关键字的用法： LIMIT [offset,] rows
     * offset 指定要返回的第一行的偏移量，pageSize 第二个指定返回行的最大数目。初始行的偏移量是0(不是1)。
     *
     * @param offset   指定要返回的第一行的偏移量
     * @param pageSize 第二个指定返回行的最大数目。初始行的偏移量是0(不是1)
     * @return {@link QueryWrapper}
     */
    public QueryWrapper limit(long offset, int pageSize) {
        query.skip(offset).limit(pageSize);
        return this;
    }

    /**
     * 根据前台分页获得数据
     *
     * @param current  当前页
     * @param pageSize 每页数量
     * @return {@link QueryWrapper}
     */
    public QueryWrapper limitPage(long current, int pageSize) {
        if (--current < 0) {
            current = 0;
        }
        query.skip(current * pageSize).limit(pageSize);
        return this;
    }

    /**
     * 强行索引指向
     * 虽然MongoDB查询优化器一般工作的很不错，但是也可以使用hint()来强迫MongoDB使用一个特定的索引。在这种方法下某些情形下会提升性能。一个有索引的collection并且执行一个多字段的查询。传入一个制定的索引，强迫查询使用该索引。
     * db.users.find({"username":"user1000", "age":30}).hint({"username":1})
     *
     * @param hint
     * @return {@link QueryWrapper}
     */
    public QueryWrapper hint(String hint) {
        assert hint != null : "hint must not be null";
        query.withHint(hint);
        return this;
    }

    /**
     * 排序
     *
     * @param column
     * @param asc
     * @return {@link QueryWrapper}
     */
    public <T> QueryWrapper sort(SFunction<T, ?> column, boolean asc) {
        assert column != null : "column must not be null";
        this.query.with(Sort.by(asc ? Direction.ASC : Direction.DESC, MongoFieldMapping.getColumnName(column)));
        return this;
    }

    /**
     * 排序
     *
     * @param column
     * @param asc
     * @return {@link QueryWrapper}
     */
    public QueryWrapper sort(String column, boolean asc) {
        assert column != null : "column must not be null";
        this.query.with(Sort.by(asc ? Direction.ASC : Direction.DESC, column));
        return this;
    }

    /**
     * 构建 QueryWrapper
     *
     * @return {@link QueryData}
     */
    public QueryData queryData() {
        QueryData body = new QueryData(query.getQueryObject().toJson(), query.getFieldsObject().toJson(), query.getSortObject().toJson());
        body.setHint(query.getHint());
        body.setSkip(query.getSkip());
        body.setLimit(query.getLimit());
        return body;
    }

    @Override
    public String toString() {
        return this.query.toString();
    }

}
